﻿using System.IO.Ports;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using Microsoft.Extensions.Logging;
using ReactiveChannelMessaging.Core;

namespace ReactiveChannelMessaging.Communication;

public enum SerialDataDirection
{
    Sent,
    Received
}

public class SerialDataMessage
{
    public string PortName { get; set; } = string.Empty;
    public byte[] Data { get; set; } = Array.Empty<byte>();
    public SerialDataDirection Direction { get; set; }
    public DateTime Timestamp { get; set; } = DateTime.Now;
}

public class SerialStatusMessage
{
    public string PortName { get; set; } = string.Empty;
    public string Status { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public DateTime Timestamp { get; set; } = DateTime.Now;
}

public class ReactiveSerialCommunication : IAsyncDisposable
{
    private readonly ILogger<ReactiveSerialCommunication> _logger;
    private readonly ReactiveMessageBus? _messageBus;
    private readonly string _portName;
    private readonly int _baudRate;
    private SerialPort? _serialPort;
    private readonly Subject<SerialDataMessage> _receivedDataSubject;
    private readonly Subject<SerialStatusMessage> _statusSubject;
    private bool _isDisposed;

    public ReactiveSerialCommunication(
        string portName,
        int baudRate,
        ILogger<ReactiveSerialCommunication> logger,
        ReactiveMessageBus? messageBus = null)
    {
        _portName = portName;
        _baudRate = baudRate;
        _logger = logger;
        _messageBus = messageBus;
        _receivedDataSubject = new Subject<SerialDataMessage>();
        _statusSubject = new Subject<SerialStatusMessage>();
    }

    public IObservable<SerialDataMessage> ReceivedData => _receivedDataSubject.AsObservable();
    public IObservable<SerialStatusMessage> StatusChanges => _statusSubject.AsObservable();
    public string PortName => _portName;
    public int BaudRate => _baudRate;
    public bool IsConnected => _serialPort?.IsOpen == true;

    public static string[] GetAvailablePorts()
    {
        try
        {
            return SerialPort.GetPortNames();
        }
        catch
        {
            return Array.Empty<string>();
        }
    }

    public async Task<bool> OpenAsync()
    {
        try
        {
            if (_serialPort?.IsOpen == true)
            {
                _logger.LogWarning("Serial port {PortName} is already open", _portName);
                return true;
            }

            _serialPort = new SerialPort(_portName, _baudRate)
            {
                DataBits = 8,
                Parity = Parity.None,
                StopBits = StopBits.One,
                Handshake = Handshake.None,
                ReadTimeout = 1000,
                WriteTimeout = 1000
            };

            _serialPort.DataReceived += OnDataReceived;
            _serialPort.ErrorReceived += OnErrorReceived;
            _serialPort.Open();

            var statusMessage = new SerialStatusMessage
            {
                PortName = _portName,
                Status = "Connected",
                Description = $"Serial port opened at {_baudRate} baud"
            };

            _statusSubject.OnNext(statusMessage);
            _logger.LogInformation("Serial port {PortName} opened successfully at {BaudRate} baud", _portName, _baudRate);

            return true;
        }
        catch (Exception ex)
        {
            var statusMessage = new SerialStatusMessage
            {
                PortName = _portName,
                Status = "Error",
                Description = $"Failed to open: {ex.Message}"
            };

            _statusSubject.OnNext(statusMessage);
            _logger.LogError(ex, "Failed to open serial port {PortName}", _portName);
            return false;
        }
    }

    public async Task CloseAsync()
    {
        try
        {
            if (_serialPort?.IsOpen == true)
            {
                _serialPort.DataReceived -= OnDataReceived;
                _serialPort.ErrorReceived -= OnErrorReceived;
                _serialPort.Close();

                var statusMessage = new SerialStatusMessage
                {
                    PortName = _portName,
                    Status = "Disconnected",
                    Description = "Serial port closed"
                };

                _statusSubject.OnNext(statusMessage);
                _logger.LogInformation("Serial port {PortName} closed", _portName);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error closing serial port {PortName}", _portName);
        }

        await Task.CompletedTask;
    }

    private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        try
        {
            if (_serialPort?.IsOpen != true) return;

            var bytesToRead = _serialPort.BytesToRead;
            if (bytesToRead == 0) return;

            var buffer = new byte[bytesToRead];
            var bytesRead = _serialPort.Read(buffer, 0, bytesToRead);

            if (bytesRead > 0)
            {
                var actualData = new byte[bytesRead];
                Array.Copy(buffer, actualData, bytesRead);

                var message = new SerialDataMessage
                {
                    PortName = _portName,
                    Data = actualData,
                    Direction = SerialDataDirection.Received,
                    Timestamp = DateTime.Now
                };

                _receivedDataSubject.OnNext(message);
                _logger.LogDebug("Received {Length} bytes from serial port {PortName}", bytesRead, _portName);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error reading data from serial port {PortName}", _portName);
        }
    }

    private void OnErrorReceived(object sender, SerialErrorReceivedEventArgs e)
    {
        var statusMessage = new SerialStatusMessage
        {
            PortName = _portName,
            Status = "Error",
            Description = $"Serial error: {e.EventType}"
        };

        _statusSubject.OnNext(statusMessage);
        _logger.LogError("Serial port {PortName} error: {ErrorType}", _portName, e.EventType);
    }

    public async ValueTask DisposeAsync()
    {
        if (_isDisposed) return;

        await CloseAsync();
        _serialPort?.Dispose();
        _receivedDataSubject?.Dispose();
        _statusSubject?.Dispose();
        _isDisposed = true;
        _logger.LogInformation("ReactiveSerialCommunication disposed");
    }

    public async Task<bool> SendStringAsync(string data)
    {
        byte[] dataBytes = Encoding.UTF8.GetBytes(data);
        return await SendBytesAsync(dataBytes);
    }

    public async Task<bool> SendBytesAsync(byte[] data)
    {
        try
        {
            if (_serialPort?.IsOpen != true)
            {
                _logger.LogWarning("Serial port {PortName} is not open. Cannot send data.", _portName);
                return false;
            }

            await _serialPort.BaseStream.WriteAsync(data, 0, data.Length);
            _logger.LogDebug("Sent {Length} bytes to serial port {PortName}", data.Length, _portName);

            var message = new SerialDataMessage
            {
                PortName = _portName,
                Data = data,
                Direction = SerialDataDirection.Sent,
                Timestamp = DateTime.Now
            };
            _receivedDataSubject.OnNext(message);
            return true;
        }
        catch (Exception ex)
        {
            var statusMessage = new SerialStatusMessage
            {
                PortName = _portName,
                Status = "Error",
                Description = $"Failed to send data: {ex.Message}"
            };
            _statusSubject.OnNext(statusMessage);
            _logger.LogError(ex, "Error sending data to serial port {PortName}", _portName);
            return false;
        }
    }

    public async Task<bool> SendHexStringAsync(string hexData)
    {
        try
        {
            if (_serialPort?.IsOpen != true)
            {
                _logger.LogWarning("Serial port {PortName} is not open. Cannot send data.", _portName);
                return false;
            }

            // 将十六进制字符串转换为字节数组
            byte[] dataBytes = HexStringToByteArray(hexData);

            await _serialPort.BaseStream.WriteAsync(dataBytes, 0, dataBytes.Length);
            _logger.LogDebug("Sent {Length} bytes to serial port {PortName}", dataBytes.Length, _portName);

            var message = new SerialDataMessage
            {
                PortName = _portName,
                Data = dataBytes,
                Direction = SerialDataDirection.Sent,
                Timestamp = DateTime.Now
            };
            _receivedDataSubject.OnNext(message);
            return true;
        }
        catch (Exception ex)
        {
            var statusMessage = new SerialStatusMessage
            {
                PortName = _portName,
                Status = "Error",
                Description = $"Failed to send data: {ex.Message}"
            };
            _statusSubject.OnNext(statusMessage);
            _logger.LogError(ex, "Error sending data to serial port {PortName}", _portName);
            return false;
        }
    }

    private byte[] HexStringToByteArray(string hex)
    {
        // 移除所有空格
        hex = hex.Replace(" ", string.Empty);

        int numberChars = hex.Length;
        if (numberChars % 2 != 0)
        {
            throw new ArgumentException("Hex string must have an even number of characters.");
        }

        byte[] bytes = new byte[numberChars / 2];
        for (int i = 0; i < numberChars; i += 2)
        {
            bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
        }
        return bytes;
    }

}
